﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;

namespace PowerPointController
{
    class GlobalHotkeyHandler : IDisposable
    {
        public event EventHandler HotkeyPressed;

        #region API-Interoperability
        [DllImport("user32", SetLastError = true)]
        private static extern int RegisterHotKey(IntPtr hwnd, int id, int fsModifiers, int vk);
        [DllImport("user32", SetLastError = true)]
        private static extern int UnregisterHotKey(IntPtr hwnd, int id);
        [DllImport("kernel32", SetLastError = true)]
        private static extern short GlobalAddAtom(string lpString);
        [DllImport("kernel32", SetLastError = true)]
        private static extern short GlobalDeleteAtom(short nAtom);

        private const int MOD_ALT = 1;
        private const int MOD_CONTROL = 2;
        private const int MOD_SHIFT = 4;
        private const int MOD_WIN = 8;

        private const int WM_HOTKEY = 0x312;
        #endregion

        [Flags()]
        public enum Modifiers
        { 
            Alt = MOD_ALT,
            Control = MOD_CONTROL,
            Shift = MOD_SHIFT,
            Windows = MOD_WIN
        }

        // the id for the hotkey
        private Form _owner;
        private HotKeyMessageFilter _hotKeyMessageFilter;

        public GlobalHotkeyHandler(Form owner)
        {
            _owner = owner;
            _hotKeyMessageFilter = new HotKeyMessageFilter(this);
        }

        #region Properties
        public Modifiers KeyModifiers { get; private set; }
        public Keys Key { get; private set; }
        public bool IsRegistered { get; private set; }

        private short HotkeyId { get; set; }
        #endregion

        #region Methods
        public void RegisterGlobalHotKey(Keys hotkey, Modifiers modifiers)
        {
            if (IsRegistered)
            {
                UnregisterGlobalHotKey();
            }

            try
            {
                string atomName = this.GetHashCode().ToString();
                HotkeyId = GlobalAddAtom(atomName);

                if (HotkeyId == 0)
                {
                    throw new Exception(
                        "Unable to generate unique hotkey ID. Error code: " + 
                        Marshal.GetLastWin32Error().ToString());
                }

                // register the hotkey, throw if any error
                if (RegisterHotKey(_owner.Handle, HotkeyId, (int)modifiers, (int)hotkey) == 0)
                {
                    throw new Exception(
                        "Unable to register hotkey. Error code: " + 
                        Marshal.GetLastWin32Error().ToString());
                }

                Application.AddMessageFilter(_hotKeyMessageFilter);

                Key = hotkey;
                KeyModifiers = modifiers;

                IsRegistered = true;
            }
            catch (Exception)
            {
                // clean up if hotkey registration failed
                UnregisterGlobalHotKey();
            }
        }

        private void UnregisterGlobalHotKey()
        {
            if (this.HotkeyId != 0)
            {
                UnregisterHotKey(_owner.Handle, HotkeyId);
                // clean up the atom list
                GlobalDeleteAtom(HotkeyId);
                HotkeyId = 0;

                Application.RemoveMessageFilter(_hotKeyMessageFilter);
            }
        }

        private void OnHotKeyPressed()
        {
            if (HotkeyPressed != null)
            {
                HotkeyPressed(this, EventArgs.Empty);
            }
        }
        #endregion

        private class HotKeyMessageFilter : IMessageFilter
        {
            GlobalHotkeyHandler _globalHotKeyHandler;

            public HotKeyMessageFilter(GlobalHotkeyHandler globalHotKeyHandler)
            {
                _globalHotKeyHandler = globalHotKeyHandler;
            }

            public bool PreFilterMessage(ref Message m)
            {
                if (m.Msg == GlobalHotkeyHandler.WM_HOTKEY && m.WParam == (IntPtr)_globalHotKeyHandler.HotkeyId)
                {
                    // do whatever you wish to do when the hotkey is pressed
                    // in this example we activate the form and display a messagebox
                    _globalHotKeyHandler.OnHotKeyPressed();
                }
                return false;
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            UnregisterGlobalHotKey();
        }

        #endregion
    }
}
